import type { PropType, ExtractPropTypes } from 'vue';
import { cloneVNode, defineComponent } from 'vue';
import PropTypes from '../_util/vue-types';
import { flattenChildren, getPropsSlot } from '../_util/props-util';
import warning from '../_util/warning';
import type { BreadcrumbItemProps } from './BreadcrumbItem';
import BreadcrumbItem from './BreadcrumbItem';
import Menu from '../menu';
import useConfigInject from '../config-provider/hooks/useConfigInject';
import useStyle from './style';
import type { CustomSlotsType, VueNode } from '../_util/type';

export interface Route {
  path: string;
  breadcrumbName: string;
  children?: Omit<Route, 'children'>[];
}

export const breadcrumbProps = () => ({
  prefixCls: String,
  routes: { type: Array as PropType<Route[]> },
  params: PropTypes.any,
  separator: PropTypes.any,
  itemRender: {
    type: Function as PropType<
      (opt: { route: Route; params: unknown; routes: Route[]; paths: string[] }) => VueNode
    >,
  },
});

export type BreadcrumbProps = Partial<ExtractPropTypes<ReturnType<typeof breadcrumbProps>>>;

function getBreadcrumbName(route: Route, params: unknown) {
  if (!route.breadcrumbName) {
    return null;
  }
  const paramsKeys = Object.keys(params).join('|');
  const name = route.breadcrumbName.replace(
    new RegExp(`:(${paramsKeys})`, 'g'),
    (replacement, key) => params[key] || replacement,
  );
  return name;
}
function defaultItemRender(opt: {
  route: Route;
  params: unknown;
  routes: Route[];
  paths: string[];
}): VueNode {
  const { route, params, routes, paths } = opt;
  const isLastItem = routes.indexOf(route) === routes.length - 1;
  const name = getBreadcrumbName(route, params);
  return isLastItem ? <span>{name}</span> : <a href={`#/${paths.join('/')}`}>{name}</a>;
}

export default defineComponent({
  compatConfig: { MODE: 3 },
  name: 'ABreadcrumb',
  inheritAttrs: false,
  props: breadcrumbProps(),
  slots: Object as CustomSlotsType<{
    separator: any;
    itemRender: { route: Route; params: any; routes: Route[]; paths: string[] };
    default: any;
  }>,
  setup(props, { slots, attrs }) {
    const { prefixCls, direction } = useConfigInject('breadcrumb', props);
    const [wrapSSR, hashId] = useStyle(prefixCls);
    const getPath = (path: string, params: unknown) => {
      path = (path || '').replace(/^\//, '');
      Object.keys(params).forEach(key => {
        path = path.replace(`:${key}`, params[key]);
      });
      return path;
    };

    const addChildPath = (paths: string[], childPath: string, params: unknown) => {
      const originalPaths = [...paths];
      const path = getPath(childPath || '', params);
      if (path) {
        originalPaths.push(path);
      }
      return originalPaths;
    };

    const genForRoutes = ({
      routes = [],
      params = {},
      separator,
      itemRender = defaultItemRender,
    }: any) => {
      const paths = [];
      return routes.map((route: Route) => {
        const path = getPath(route.path, params);

        if (path) {
          paths.push(path);
        }
        const tempPaths = [...paths];
        // generated overlay by route.children
        let overlay = null;
        if (route.children && route.children.length) {
          overlay = (
            <Menu
              items={route.children.map(child => ({
                key: child.path || child.breadcrumbName,
                label: itemRender({
                  route: child,
                  params,
                  routes,
                  paths: addChildPath(tempPaths, child.path, params),
                }),
              }))}
            ></Menu>
          );
        }
        const itemProps: BreadcrumbItemProps = { separator };
        if (overlay) {
          itemProps.overlay = overlay;
        }
        return (
          <BreadcrumbItem {...itemProps} key={path || route.breadcrumbName}>
            {itemRender({ route, params, routes, paths: tempPaths })}
          </BreadcrumbItem>
        );
      });
    };
    return () => {
      let crumbs: VueNode[];

      const { routes, params = {} } = props;

      const children = flattenChildren(getPropsSlot(slots, props));
      const separator = getPropsSlot(slots, props, 'separator') ?? '/';

      const itemRender = props.itemRender || slots.itemRender || defaultItemRender;
      if (routes && routes.length > 0) {
        // generated by route
        crumbs = genForRoutes({
          routes,
          params,
          separator,
          itemRender,
        });
      } else if (children.length) {
        crumbs = children.map((element, index) => {
          warning(
            typeof element.type === 'object' &&
              (element.type.__ANT_BREADCRUMB_ITEM || element.type.__ANT_BREADCRUMB_SEPARATOR),
            'Breadcrumb',
            "Only accepts Breadcrumb.Item and Breadcrumb.Separator as it's children",
          );
          return cloneVNode(element, { separator, key: index });
        });
      }

      const breadcrumbClassName = {
        [prefixCls.value]: true,
        [`${prefixCls.value}-rtl`]: direction.value === 'rtl',
        [`${attrs.class}`]: !!attrs.class,
        [hashId.value]: true,
      };

      return wrapSSR(
        <nav {...attrs} class={breadcrumbClassName}>
          <ol>{crumbs}</ol>
        </nav>,
      );
    };
  },
});
